package taller2.model.graph.algorithms;

import java.util.ArrayList;
import java.util.List;

import taller2.interfaces.Connection;
import taller2.interfaces.Graph;
import taller2.interfaces.Node;
import taller2.utils.Constants;
import taller2.utils.Constants.Colores;
import taller2.utils.Utils;

public class FloydApplicator extends AlgorithmApplicatorImpl{
    	private static final String savePath = Constants.savedGraphsPath + "Floyd";
    	private ArrayList<String> tablas;
	private ArrayList<String> antesDePivotear,despuesDePivotear;
	private ArrayList<Integer> highligtedLines;
	private int cont,nodeCant;

		
	public FloydApplicator(){
		super();
	}
		
        @Override
        public void solveProblem(Graph g) {         
        	
    		if(noGraph(g) || !noGraphPaths() || !noPseudocodeLines())
    			return;
    		
    		g.killFluxes();
            g.unselectConnections();                
            g.unselectNodes();                      
            	
            //Salvo estado inicial
            g.save(savePath+0);

            /* si no es dirigido ni ponderado no funca!!!*/
            if(!g.isDirected())
            	return;
                    	
           	List<Node> nodes = g.getNodes();
            nodeCant = nodes.size();
            int[][] matrizCostoCamino = new int[nodeCant][nodeCant];

            for(int i = 0; i < nodeCant; i++){
            	for(int j = 0; j < nodeCant; j++){
            		if(i==j)
               			matrizCostoCamino[i][j] = 0;
            		else
            			matrizCostoCamino[i][j] = Constants.Inf;
            
            	}
            }
           
            for(int i=0; i<nodes.size();i++){
            	Node node = nodes.get(i);
            	node.setId(i);
            }
            	
           	for (Node node : nodes){
           		for(Connection connection : node.getConnections()){
           			int i = node.getNumberId();
           			int j = connection.getOtherNode().getNumberId();
                    
          			if (i != j)
           				matrizCostoCamino[i][j] = connection.getWeight();
            	}
            }

            cont = 0;
            cont++;
            guardar(savePath+0,0,matrizCostoCamino,nodeCant,-1);
            g.save(savePath+cont);

            for(int k = 0; k < nodeCant; k++) {
            	
            	cont++;
            	guardar(savePath+cont,2,matrizCostoCamino,nodeCant,-1);
            	g.save(savePath+cont);
            	guardarTabla(matrizCostoCamino,nodeCant,antesDePivotear);

            	for (int i = 0; i < nodeCant; i++) {
            		
            		guardar(savePath+cont,3,matrizCostoCamino,nodeCant,calcularNumeroResaltado(matrizCostoCamino,nodeCant));
            		for (int j = 0; j < nodeCant; j++) {
            			
                		cont++;
                		guardar(savePath+cont,4,matrizCostoCamino,nodeCant,calcularNumeroResaltado(matrizCostoCamino,nodeCant));
                		g.save(savePath+cont);
                		
                		marcarNodos(g,i,j,k);
                		
                		cont++;
                		guardar(savePath+cont,5,matrizCostoCamino,nodeCant,calcularNumeroResaltado(matrizCostoCamino,nodeCant));
                		g.save(savePath+cont);
                		
                		if (i != j){
                			int distance = matrizCostoCamino[i][k] + matrizCostoCamino[k][j];
                			cont++;
                			guardar(savePath+cont,6,matrizCostoCamino,nodeCant,calcularNumeroResaltado(matrizCostoCamino,nodeCant));	
                			g.save(savePath+cont);

                			if (matrizCostoCamino[i][j] > distance){
                        		matrizCostoCamino[i][j] = distance;
                        		marcarCaminos(g,i,j,k,matrizCostoCamino);
                        			
                        		cont++;
                        		guardar(savePath+cont,7,matrizCostoCamino,nodeCant,calcularNumeroResaltado(matrizCostoCamino,nodeCant));	
                               	g.save(savePath+cont);

                			}
                		}
                		desmarcar(g,i,j,k);   
                	}
                }
            guardarTabla(matrizCostoCamino,nodeCant,despuesDePivotear);
            }
            
            guardar(savePath+0,8,matrizCostoCamino,nodeCant,-1);
            
            g.load(savePath+0);              
        }
      
		private int calcularNumeroResaltado(int tabla[][],int tam){
        	String tablaActual = tablas.get(tablas.size()-1);
        	String tablaAnterior = tablas.get(tablas.size()-2);

        	return buscarDiferenciasEntreTablas(tablaActual,tablaAnterior,tam);        	        		
		}

        private void guardar(String pathGrafo,int lineaPseudocodigo,int[][] tabla,int tamTabla, int numeroResaltado){
            graphPaths.add(pathGrafo);
        	pseudocodeLines.add(lineaPseudocodigo);
        	guardarTabla(tabla,tamTabla,tablas);
        	
        	highligtedLines.add(numeroResaltado);
        }
        
        private void desmarcar(Graph g, int i, int j, int k) {
			Node n1 = g.getNodes().get(i);
			Node n2 = g.getNodes().get(j);
			Node n3 = g.getNodes().get(k);
			
			n1.changeColor(Colores.yellow);
			n2.changeColor(Colores.yellow);
			n3.changeColor(Colores.yellow);
			
			g.unselectNodes();
			g.unselectConnections();
		}

        private void marcarNodos(Graph g, int startIndex, int endIndex, int intermediateIndex) {
			ArrayList<Node> list = g.getNodes();
			
			Node start = list.get(startIndex);
			Node end = list.get(endIndex);
			Node intermediate = list.get(intermediateIndex);
			//para uçque si es inicio o fin e intermedio al mismo tiempo quede pintado de forma mas clara
			// (color de inicio o fin "le ganan" a intermedio)
			intermediate.addColor(Colores.red);
			end.addColor(Colores.purple);
			start.addColor(Colores.green);

		}
        
		private void marcarCaminos(Graph g, int startIndex, int endIndex, int intermediateIndex, int paths[][]) {
			g.unmarkNodes();
			ArrayList<Node> list = g.getNodes();
			
			Node start = list.get(startIndex);
			Node end = list.get(endIndex);
			Node intermediate = list.get(intermediateIndex);
						
			marcarCaminoEntre(start,intermediate,paths[startIndex][intermediateIndex]);
			g.unmarkNodes();

			marcarCaminoEntre(intermediate,end,paths[intermediateIndex][endIndex]);			
			g.unmarkNodes();
		}

		private boolean marcarCaminoEntre(Node n1,Node n2,int weight){
			
			for(Connection connection : n1.getConnections()){
				int connWeight = connection.getWeight();
				Node otherNode = connection.getOtherNode();
				
				if(connWeight == weight){
					connection.select();
					return true;
				}
				
				if((connWeight < weight)&&(!otherNode.isMarked())){
					n1.mark();
					boolean finished = marcarCaminoEntre(otherNode,n2,weight-connWeight);
					
					if(finished){
						connection.select();
						return true;
					}
				}
			}
			return false;
		}
		
		private void guardarTabla(int tabla[][],int tam, ArrayList<String> destino){
        	String tablaFormateada = formatearTabla(tabla,tam);
        	destino.add(tablaFormateada);        	
        }
		
        private int buscarDiferenciasEntreTablas(String tablaActual,String tablaAnterior,int tam) {
        	for(int i=0; i<tam ; i++){
				for(int j=0; j<tam ; j++){
					int valor = Utils.obtenerPos(tablaActual,i,j,tam,"[","]");
					int valor2 = Utils.obtenerPos(tablaAnterior,i,j,tam,"[","]");
					
					if(valor != valor2)
						return Utils.matrixPositionToArrayPosition(i,j,tam);
				}
			}
			return -1;
		}

		private String formatearTabla(int tabla[][],int tam){
    		String tablaFormateada = "";

        	if(tabla != null){        		
        		tablaFormateada+= "\n\t";
        		
        		for(int i=0;i<tam;i++)
        			tablaFormateada+= String.valueOf(i)+"\t   ";
        		tablaFormateada+= "\n";
        		
        		for(int i=0;i<tam;i++){
    				tablaFormateada+= String.valueOf(i);
        			for(int j=0;j<tam;j++){
        				String term;
        				if(tabla[i][j] != Constants.Inf)
        					term = String.valueOf(tabla[i][j]);
        				else
        					term = "Inf";
        				
        					tablaFormateada+= "\t[" + term + "]  ";
        			}
        			tablaFormateada+= "\n";
        		}
        		tablaFormateada+= "\n";
        	}
        	return tablaFormateada;
        }
   
        @Override
        public String getAuxText(){
    		if (noTables())
    			return "";
    			
    		String line = tablas.get(position);
    		return line;
    	} 
        
    	// returns true if the list of tables is empty
    	private boolean noTables(){
    		return (tablas == null) || (tablas.isEmpty());
    	}
        
    	// resets the applicator to change the graph
    	// and solve the problem again
    	public void reset(){
    		tablas = new ArrayList<String>();
    		highligtedLines = new ArrayList<Integer>();
    		graphPaths = new ArrayList<String>();
    		pseudocodeLines = new ArrayList<Integer>();
    		antesDePivotear = new ArrayList<String>();
    		despuesDePivotear  = new ArrayList<String>();
    		auxText = new ArrayList<String>();
    		position = 0;
    		nodeCant = 0;
    	}        

    	public int getNodeCant(){
    		return nodeCant;	
    	}

	public String getEndMatrixUsingNode(int n){
		if(n >= despuesDePivotear.size())	
			return "";	

		return despuesDePivotear.get(n);
	}
        
	public String getStartingMatrixUsingNode(int n){
		if(n >= antesDePivotear.size())	
			return "";	

		return antesDePivotear.get(n);
	}

    	public void save(String path){
    	};

    	public void load(String path){
    	}

		@Override
		public int getHighlightedWordNumber() {
			if(highligtedLines.isEmpty())
				return -1;
			return highligtedLines.get(position);
		};
}
